/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package cz.mzk.oai.metadataRepository.repository.collections;

import cz.mzk.oai.metadataRepository.repository.records.NoSuchRecordException;
import cz.mzk.oai.metadataRepository.repository.records.inMemory.RecordInMemory;
import cz.mzk.oai.metadataRepository.transformation.SecondaryRecordsBuilder;
import cz.mzk.oai.metadataRepository.metadata.DateStamp;
import cz.mzk.oai.metadataRepository.metadata.MetadataFormat;
import cz.mzk.oai.metadataRepository.repository.Identifier;
import cz.mzk.oai.metadataRepository.repository.OaiSet;
import cz.mzk.oai.metadataRepository.repository.records.Record;
import cz.mzk.oai.metadataRepository.repository.records.Manager;
import cz.mzk.oai.metadataRepository.repository.records.impl.ManagerFileImpl;
import cz.mzk.oai.metadataRepository.repository.records.presentAndDeleted.PresentRecord;
import cz.mzk.oai.metadataRepository.transformation.TransformationException;
import cz.mzk.oai.metadataRepository.transformation.XsltConfig;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.dom4j.Document;

/**
 * This class represent one directory structure of one collection. 
 * Whole collection and its directory contains files that all belong to the same sets.
 * For example MollMaps-before1800, Oldmaps-after1800, schramVedute-error1800
 * @author Martin Rehanek, Martin.Rehanek@gmail.com
 */
public class MetadataCollection {
    //TODO: sjednotit logovaci hlasky, rozlisit urovne logovani
    //collection data

    private static final Logger logger = Logger.getLogger(MetadataCollection.class.getName());
    private final File rootDir;
    private final String name;
    private final boolean itemsCanBeMovedFromHere;
    //TODO: rozmyslet, co udelat s bazi.
    //vypada to, ze je potreba jen pro rozhodovani, kterou vybrat kolekci
    //tj. tady vubec byt nemusi
    private final String alephBase;
    private final Set<MetadataFormat> supportedFormats;
    private final Set<OaiSet> oaiSets;
    private final Manager recordsManager;
    private SecondaryRecordsBuilder secondaryRecordsBuilder;

    public MetadataCollection(String alephBase, String name, File rootDir, Set<MetadataFormat> supportedFormats, Set<OaiSet> oaiSets, Set<XsltConfig> xsltConfigs, boolean itemsCanBeMovedFromHere) throws IOException {
        this.alephBase = alephBase;
        this.name = name;
        this.rootDir = rootDir;
        this.oaiSets = oaiSets;
        this.supportedFormats = supportedFormats;
        this.itemsCanBeMovedFromHere = itemsCanBeMovedFromHere;
        secondaryRecordsBuilder = new SecondaryRecordsBuilder(xsltConfigs);
        createCollectionDirIfNotExists();
        recordsManager = new ManagerFileImpl(this);
    }

    private void createCollectionDirIfNotExists() {
        if (!rootDir.exists()) {
            logger.log(Level.INFO, "{0} does not exist, creating", rootDir.getAbsolutePath());
            rootDir.mkdirs();
        }
    }

    public File getRootDir() {
        return rootDir;
    }

    public String getName() {
        return name;
    }

    public void addOrUpdateRecord(Identifier id, MetadataFormat format, Document doc) throws IOException {
        logger.log(Level.INFO, "id: {0}", id);
        if (doc == null) {
            logger.log(Level.INFO, "record cannot be updated");
            setRecordDeletedIfPresent(id, format);
        } else {
            logger.log(Level.INFO, "record will be updated");
            updateOrCreateRecordWithMetadata(id, format, doc);
        }
    }

    private void setRecordDeletedIfPresent(Identifier id, MetadataFormat format) throws IOException {
        if (recordsManager.contains(id, format)) {
            logger.log(Level.INFO, "record will be deleted");
            recordsManager.setRecordDeleted(id, format);
            setDerivedRecorsDeleted(id, format);
        }
    }

    private void updateOrCreateRecordWithMetadata(Identifier id, MetadataFormat format, Document doc) throws IOException {
        if (!recordsManager.contains(id, format)) {
            logger.log(Level.INFO, "new record will be created");
            recordsManager.createRecord(id, format, doc);
        }
        recordsManager.updateRecord(id, format, doc);
        PresentRecord record = (PresentRecord) recordsManager.getRecord(id, format);
        updateDerivedRecords(record);
    }

    public void updateDerivedRecords(PresentRecord primaryRecord) {
        Set<MetadataFormat> derivedFormats = secondaryRecordsBuilder.derivedFormats(primaryRecord.getMetadataFormat());
        logger.log(Level.INFO, "updating derived records from {0}", primaryRecord.getMetadataFormat());
        logger.log(Level.INFO, "formats: {0}", derivedFormats.size());
        int counter = 1;
        for (MetadataFormat derivedFormat : derivedFormats) {
            logger.log(Level.INFO, "{0}: {1}", new Object[]{counter++, derivedFormat.toString()});
            addOrUpdateDerivedRecord(primaryRecord, derivedFormat);
        }
    }

    private void addOrUpdateDerivedRecord(PresentRecord record, MetadataFormat derivedFormat) {
        try {
            logger.log(Level.INFO, "add or update derived record");
            Document secondaryMetadata = secondaryRecordsBuilder.buildSecondaryRecordMetadata(record.getMetadataFormat(), derivedFormat, record.getMetadata());
            addOrUpdateRecord(record.getId(), derivedFormat, secondaryMetadata);
        } catch (IOException ex) {
            logger.log(Level.SEVERE, null, ex);
        } catch (TransformationException ex) {
            logger.log(Level.SEVERE, null, ex);
        }
    }

    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        builder.append("===================================\n");
        builder.append(name);
        builder.append("\n-----------------------------------\n");

        builder.append("sets: ");
        for (OaiSet set : oaiSets) {
            builder.append(set.getSetSpec());
            builder.append(' ');
        }
        //builder.append("properties:\n");
        //builder.append(properties.toString());
        builder.append('\n');
        builder.append('\n');
        return builder.toString();
    }

    public Set<OaiSet> getOaiSets() {
        return oaiSets;
    }

    public boolean supportsFormat(MetadataFormat format) {
        return supportedFormats.contains(format);
    }

    public boolean containsRecord(Identifier id, MetadataFormat format) {
        return supportsFormat(format) && recordsManager.contains(id, format);
    }

    public Record getRecord(Identifier id, MetadataFormat format, boolean validate) {
        if (containsRecord(id, format)) {
            return recordsManager.getRecord(id, format);
        } else {
            return null;
        }
    }

    public List<Record> getRecords(MetadataFormat format, DateStamp from, DateStamp until) {
        if (supportsFormat(format)) {
            return recordsManager.getRecords(format, from, until);
        } else {
            return Collections.<Record>emptyList();
        }
    }

    public boolean containsItem(Identifier id) {
        for (MetadataFormat format : MetadataFormat.values()) {
            if (containsRecord(id, format)) {
                return true;
            }
        }
        return false;
    }

    public void setDeleted(Identifier id, MetadataFormat metadataFormat) throws IOException {
        logger.log(Level.INFO, "id: {0}", id);
        recordsManager.setRecordDeleted(id, metadataFormat);
    }

    public void setDerivedRecorsDeleted(Identifier id, MetadataFormat primaryFormat) throws IOException {
        Set<MetadataFormat> derivedFormats = secondaryRecordsBuilder.derivedFormats(primaryFormat);
        logger.log(Level.INFO, "deleting derived records from {0}", primaryFormat);
        logger.log(Level.INFO, "formats: {0}", derivedFormats.size());
        int counter = 1;
        for (MetadataFormat derivedFormat : derivedFormats) {
            logger.log(Level.INFO, "{0}: {1}", new Object[]{counter++, derivedFormat.toString()});
            setRecordDeletedIfPresent(id, derivedFormat);
        }
    }

    public boolean isInOaiSet(OaiSet set) {
        if (set == null || set.getSetSpec() == null) {
            return false;
        } else {
            for (OaiSet setFound : oaiSets) {
                if (setFound.getSetSpec().equals(set.getSetSpec())) {
                    return true;
                }
            }
            return false;
        }
    }

    /**
     *
     * @param id
     * @return List of RecordInMemory only for those records that are present or deleted.
     * If there was no record in some format no RecordInMemory instance is created for that format.
     * @throws IOException
     */
    public List<RecordInMemory> toRecordsInMemory(Identifier id) throws IOException {
        List<RecordInMemory> result = new ArrayList<RecordInMemory>();
        for (MetadataFormat format : supportedFormats) {
            Record record = getRecord(id, format, false);
            if (record != null) { //present or deleted record found
                result.add(new RecordInMemory(record));
            }
        }
        return result;
    }

    public void removeAllRecords(Identifier id) {
        for (MetadataFormat format : supportedFormats) {
            if (containsRecord(id, format)) {
                removeRecord(id, format);
            }
        }
    }

    private void removeRecord(Identifier id, MetadataFormat format) {
        try {
            recordsManager.deleteRecord(id, format);
        } catch (IOException ex) {
            logger.log(Level.SEVERE, null, ex);
        } catch (NoSuchRecordException ex) {
            logger.log(Level.SEVERE, null, ex);
        }
    }

    public void addRecordsFromMemory(List<RecordInMemory> records) throws IOException {
        for (RecordInMemory record : records) {
            if (record.isDeleted()) {
                recordsManager.createDeletedRecord(record.getId(), record.getMetadataFormat());
            } else {
                addOrUpdateRecord(record.getId(), record.getMetadataFormat(), record.getData());
            }
        }
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        final MetadataCollection other = (MetadataCollection) obj;
        if ((this.name == null) ? (other.name != null) : !this.name.equals(other.name)) {
            return false;
        }
        return true;
    }

    @Override
    public int hashCode() {
        int hash = 7;
        hash = 67 * hash + (this.name != null ? this.name.hashCode() : 0);
        return hash;
    }

    public boolean itemsCanBeMovedFromHere() {
        return itemsCanBeMovedFromHere;
    }
}
